# Error & Exception

  • 程序执行中发生的不正常情况称为异常。

# 异常的体系

异常的根类是java.lang.Throwable,其下有两个子类:

  • java.lang.Error

    JVM 无法解决的严重问题。如:JVM 系统内部错误、资源耗尽等严重情况。如栈 stack、堆 heap 内存耗尽StackOverflowError(可能在递归时发生)OutOfMemoryError(new 了非常多对象)。一般不编写针对性的代码进行处理。

  • java.lang.Exception

    其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。

Throwable 中的常用方法:

  • public void printStackTrace()打印异常的详细信息

    包含了异常的类型,异常的原因 ,还包括异常出现的位置,在开发和调试阶段,都得使用 printStackTrace

  • public String getMessage()获取发生异常的原因。提示给用户的时候,就提示错误原因。

# 异常的分类

我们平常说的异常就是指 Exception,因为这类异常一旦出现,我们就要对代码进行更正,修复程序。对于这些错误,一般有两种解决方法:一是遇到错误就终止程序 的运行。另一种方法是由程序员在编写程序时,就考虑到错误的 检测、错误消息的提示,以及错误的处理。

捕获错误最理想的是在编译期间,但有的错误只有在运行时才会发生。 比如:除数为 0,数组下标越界等。

异常(Exception)的分类:根据在编译时期还是运行时期去检查异常。Java 语言规范将派生于Error类和RuntimeException类的所有异常称为非受查异常(unchecked 异常),其他为受查异常(checked 异常)

  • 编译时期异常:在编译时期就会检查,如果没有处理异常则编译失败。(IO、日期格式化异常)
  • 运行时期异常:在编译时期运行异常不会被检测,即不报错,但在运行时期检查异常。如:数学异常。运行时发生时一定是程序的问题,不完善,需要程序员做好校验等工作!

image-20190916232901753

# 异常的处理

关于异常对象的产生:

  • 系统自动生成的异常对象
  • 手动的生成一个异常对象,并抛出(throw)

Java 提供的是异常处理的抓抛模型

  • 抓:可以理解为异常的处理方式:try-catch-finally 、 throws
  • 抛:程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象。该异常对象将被提交给 Java 运行时系统,这个过程称为抛出(throw)异常。一旦抛出对象以后,其后的代码就不再执行。

# 抓—捕获异常 try-catch-finally

捕获异常:Java 中对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理。

try {
    //可能产生异常的代码
} catch (异常类名  变量名) {
    //异常的处理逻辑,异常异常对象之后,怎么处理异常对象
    //记录日志/打印异常信息/继续抛出异常
}
...
catch (异常类名 变量名) {

} finally {
    //一定会执行的代码
}
  • 使用 try 将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去 catch 中进行匹配。一旦 try 中的异常对象匹配到某一个 catch 时,就进入 catch 中进行异常的处理。一旦处理完成,就跳出当前的 try-catch 结构(只匹配一个。在没有写 finally 的情况)。继续执行其后的代码。

  • catch 中的异常类型如果没有子父类关系,则谁声明在上,谁声明在下无所谓。也可以放入一个 catch 中。

    catch 中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面。否则,报错。

  • 常用的异常对象处理的方式:

    • String getMessage()
    • printStackTrace()
  • 在 try 结构中声明的变量,再出了 try 结构以后,就不能再被调用
  • try-catch-finally 结构可以嵌套
  • 多个异常使用捕获该如何处理
    • 多个异常分别处理
    • 多个异常一次捕获,多次处理(若捕获的异常有子父类关系父类放下面没有可以放在一个 catch 中
    • 多个异常一次捕获一次处理
  • 运行时异常被抛出可以不处理。即不捕获也不声明抛出。

# finally 代码块

finallytry中异常语句后的代码不被执行,必须要执行的可以放在finally中,如释放系统资源。但是当在try...catch...执行System.exit(0)(表示退出当前 Java 虚拟机),finally才不会执行

语法:try...catch....finally,不能单独使用

  • 如果finally 有 return 语句,将覆盖原始的返回值,永远返回 finally 中的值。一般应避免该情况

    public static int fin() {
        int a = 10;
        try {
            return a;
        } catch (Exception e) {
            System.out.println(e.getMessage());
        } finally {
            a = 40;
            return a; //最终返回40。这里若修改为return 4,最终就返回4
            // 若没有return a; 这行代码,则无论a怎么变化,还是会返回10;
        }
    }
    

# 抓—声明异常 throws

关键字throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常

修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2{   }
  • 注意:

    • throws 关键字必须写在方法声明处,指明此方法执行时,可能会抛出的异常类型。

    • 一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足 throws 后异常

      类型时,就会被抛出。异常代码后续的代码,就不再执行!

    • throws 关键字后边声明的异常必须是Exception 或者是 Exception 的子类

    • 方法内部如果抛出了多个异常对象,那么 throws 后边必须也声明多个异常

      如果抛出的多个异常对象有子父类关系,那么直接声明父类异常即可

    • 调用了一个声明抛出异常的方法,我们就必须的处理声明的异常

      • 要么try-catch自己处理异常
      • 要么继续使用throws声明抛出,交给方法的调用者处理,最终交给 JVM

开发中如何选择使用 try-catch-finally 还是使用 throws?

  • 如果父类中被重写的方法没有 throws 方式处理异常,则子类重写的方法也不能使用 throws,意味着如果子类重写的方法中有异常,必须使用 try-catch-finally 方式处理
  • 执行的方法 a 中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用 throws的方式进行处理。而执行的方法 a 可以考虑使用 try-catch-finally 方式进行处理。(可能 try-catch 已经捕获了异常,之后的需要的数据没获取到)

# 抛—抛出异常 throw

在编写程序时,我们必须要考虑程序出现问题的情况。比如,在定义方法时,方法需要接受参数。那么,当调用方法使用接受到的参数时,首先需要先对参数数据进行合法的判断,数据若不合法,就应该告诉调用者,传递合法的数据进来。这时需要使用抛出异常的方式来告诉调用者。

  • 在 java 中,提供了一个 throw 关键字,throw 用在方法内,抛出一个指定的异常对象

    • 创建一个异常对象,封装一些提示信息(信息可以自己编写)。

    • 通过关键字throw将这个异常对象告知给调用者,并结束当前方法的执行

      throw new 异常类名(参数);
      
  • 注意:

    • throw 关键字必须写在方法的内部
    • throw 关键字后边 new 的对象必须是Exception 或者 Exception 的子类对象
    • throw 关键字抛出指定的异常对象,我们就必须处理这个异常对象
      • throw 关键字后边创建的是RuntimeException或是其子类对象,可以不处理,默认交给 JVM 处理
      • throw 关键字后边创建的是编译异常(写代码的时候报错),我们就必须处理,要么 throws,要么 try...catch

# 子父类异常注意事项

  • 父类的方法抛出或不抛出异常,子类重写的方法抛出的异常必须小于等于父类抛出的异常(多态)

  • 父类的方法抛出一个或多个异常子类重写的方法抛出的异常必须与父类相同或是其子类不抛

  • 父类的方法没有异常抛出,子类重写的方法不能有异常抛出。若产生异常则只能捕获处理

# 自定义异常

  • 自定义的异常类继承ExceptionRuntimeException(尽量继承这个,对代码没有侵入性)

  • 定义空参构造方法和带异常信息的构造方法

  • 提供全局常量:serialVersionUID

    public class MyException extends Exception/*RuntimeException*/ {
        static final long serialVersionUID = 1L;
    
        public MyException() {
        }
    
        public MyException(String message) {
            super(message);
        }
    }
    

# 习题

# 常见的异常,举例说明

  • RuntimeException(unchecked)

    • NullPointerException

    • ArithmeticException

      int a = 10;
      int b = 0;
      System.out.println(a / b);
      
    • ArrayIndexOutOfBoundsException

    • NumberFormatException

      String str = "123";
      str = "abc";
      int num = Integer.parseInt(str);
      
    • InputMismatchException

      Scanner scanner = new Scanner(System.in);
      int score = scanner.nextInt();
      System.out.println(score);
      scanner.close();
      
    • ClassCastException

      Object obj = new Date();
      String str = (String)obj;
      
    • ……

  • java.io.IOException

    • java.io.FileNotFoundException
  • java.lang.ClassNotFoundException

  • java.sql.SQLException

  • java.lang.InterruptedException

# throwthrows区别

  • throw:在方法体中,并且抛出一个异常对象。程序执行到 t 此时立即停止,它后面的语句都不执行。

    抛出的是异常对象 ,说明这里肯定有异常产生。一般用于自定义异常,体现在选择语句中

  • throws:在方法声明上,后面跟异常的类名,可以是多个,调用者处理

    声明方法有异常,是一种可能性,这个异常不一定会产生

# final,finally,finallize区别

  • final:最终意思,可以修饰类、成员变量、成员方法。详见此
  • finally:异常处理,用于释放资源,finally 中的代码一定会被执行,除非执行之前 jvm 退出
  • finalize:Object 类的一个方法,用于垃圾回收

# throw 后代码

throw 语句后不能跟其他代码,否则永远执行不到,编译错误

try {
    throw new Exception();
    System.out.println("怎么也执行不到,编译失败");
} catch (Exception e) {
    e.printStackTrace();
}

# 习题 1

public class ReturnExceptionDemo {
    static void methodA() {
        try {
            System.out.println("进入方法A");
            throw new RuntimeException("制造异常");
        } finally {
            System.out.println("用A方法的finally");
        }
    }

    static void methodB() {
        try {
            System.out.println("进入方法B");
            return;
        } finally {
            System.out.println("调用B方法的finally");
        }
    }

    public static void main(String[] args) {
        try {
            methodA();
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        methodB();

        // 进入方法A
        // 用A方法的finally
        // 制造异常
        // 进入方法B
        // 调用B方法的finally
    }
}